#ifndef __ovip_third_nutshell_simple_ram__
#define __ovip_third_nutshell_simple_ram__
#include "nutshell_simplebus.h"
#include "xspcomm/xclock.h"
#include "openvip/mem.h"
#include "xspcomm/xcoroutine.h"

namespace ovip {
    class SimpleRam
    {
        xspcomm::XClock *clk;
        u_int64_t read_addr = 0;
        u_int64_t read_offs = 0;
        u_int64_t wdat_addr = 0;
        u_int64_t wdat_offs = 100;
        int read_index      = 0;

    public:
        std::string mName;
        std::string mPrefix;
        SimpleBusWrapper mBus;
        SimpleMemory mMem;
        SimpleRam(xspcomm::XClock &clock, std::string name, xspcomm::XPort &port,
                  std::string prefix = "", u_char default_value = 0) :
            mBus(port, prefix), mMem(name, 4, 1024, default_value)
        {
            this->mName = name;
            this->clk   = &clock;
            this->clk->StepRis([this](u_int64_t c, void *args) {
                // init
                this->mBus.port["resp_valid"] = 0;

                // handle cmd
                if (this->mBus.port["req_valid"] == 1) {
                    // 8 time read
                    if (this->mBus.IsReqReadBurst()) {
                        Assert(this->mBus.port["req_bits_size"] == 3,
                               "burs read need size(%ld) = 3",
                               this->mBus.port["req_bits_size"].U());
                        Assert(this->read_index == 0,
                               "last ready is not complete!");
                        this->read_index = 8;
                        this->read_offs = this->mBus.port["req_bits_addr"].U() % 64;
                        this->read_addr =
                            this->mBus.port["req_bits_addr"].U() - this->read_offs;
                        // 8 time write
                    } else if (this->mBus.IsReqWriteBurst()) {
                        // Debug("IsReqWriteBurst");
                        if (this->wdat_offs >= 7) {
                            this->wdat_offs = 0;
                            this->wdat_addr = this->mBus.port["req_bits_addr"].U();
                        } else {
                            this->wdat_offs += 1;
                        }
                        this->mMem.Write(this->wdat_addr + this->wdat_offs * 8,
                                         8, this->mBus.port["req_bits_wdata"].U());
                        this->mBus.RespWriteData(this->mBus.cmd_writeResp, 0);
                        Assert(this->wdat_offs < 7,
                               "normal write offset(%ld) need < 7",
                               this->wdat_offs);
                        // 8 time last write
                    } else if (this->mBus.IsReqWriteLast()) {
                        this->wdat_offs += 1;
                        this->mMem.Write(this->wdat_addr + this->wdat_offs * 8,
                                         8, this->mBus.port["req_bits_wdata"].U());
                        this->mBus.RespWriteData(this->mBus.cmd_writeResp, 0);
                        Assert(this->wdat_offs == 7,
                               "last write offset(%ld) need = 7",
                               this->wdat_offs);
                        // mmio write
                    } else if (this->mBus.IsReqWrite()) {
                        // MMIO write
                        this->mMem.Write(this->mBus.port["req_bits_addr"].U(), 8,
                                         this->mBus.port["req_bits_wdata"].U());
                        this->mBus.RespWriteData(this->mBus.cmd_writeResp, 0);
                        // mmio read
                    } else if (this->mBus.IsReqRead()) {
                        // MMIO Read
                        this->mBus.RespReadData(
                            this->mBus.cmd_readLast,
                            this->mMem.Read(this->mBus.port["req_bits_addr"].U(),
                                            8));
                    } else {
                        Assert(false, "%s.request.cmd (%ld) is not supported!",
                               this->mName.c_str(),
                               this->mBus.port["req_bits_cmd"].U());
                    }
                }

                // ram read 8 times;
                if (this->read_index != 0
                    && this->mBus.port["resp_ready"].U() == 1) {
                    u_int8_t cmd = 0;
                    if (this->read_index == 1) {
                        cmd = this->mBus.cmd_readLast;
                    }
                    u_int64_t ram_data =
                        this->mMem.Read(this->read_addr + this->read_offs, 8);
                    this->mBus.RespReadData(cmd, ram_data);
                    this->read_index -= 1;
                    this->read_offs = (this->read_offs + 8) % 64;
                }

                // check constrains
                Assert(this->read_index >= 0, "read index need >=0");
            });

            // always be req-ready and resp-invalid
            this->mBus.port["req_ready"]  = 1;
            this->mBus.port["resp_valid"] = 0;
        }
    };

#if ENABLE_XCOROUTINE
    class SimpleRamCor
    {
        xspcomm::XClock *clk;

    public:
        std::string mName;
        std::string mPrefix;
        SimpleBusWrapper mBus;
        SimpleMemory mMem;
        bool debug;
        SimpleRamCor(xspcomm::XClock &clock, std::string name, xspcomm::XPort &port,
                     std::string prefix = "", u_char default_value = 0,
                     bool debug = false) :
            mBus(port, prefix), mMem(name, 4, 1024, default_value), debug(debug)
        {
            this->mName                   = name;
            this->clk                     = &clock;
            this->mBus.port["req_ready"]  = 1;
            this->mBus.port["resp_valid"] = 0;
            this->do_task();
        }

        xspcomm::xcorutine<> do_task()
        {
            while (true) {
                // handle cmd
                // 8 time read
                bool need_wait = true;
                if (this->mBus.IsReqReadBurst()) {
                    need_wait = false;
                    Assert(this->mBus.port["req_bits_size"] == 3,
                           "burs read need size(%ld) = 3",
                           this->mBus.port["req_bits_size"].U());

                    auto read_offs = this->mBus.port["req_bits_addr"].U() % 64;
                    auto read_addr =
                        this->mBus.port["req_bits_addr"].U() - read_offs;

                    for (int i = 0; i < 8; i++) {
                        u_int64_t ram_data =
                            this->mMem.Read(read_addr + read_offs, 8);
                        u_int8_t cmd = (i == 7 ? this->mBus.cmd_readLast :
                                                 this->mBus.cmd_read);
                        this->mBus.RespReadData(cmd, ram_data);
                        this->mBus.port["resp_valid"] = 1; // Send data
                        DebugC(
                            this->debug,
                            "[%ld] %s.read burst at: %lx (%lx) [%d], data: 0x%lx, req valid=%lx",
                            this->clk->clk, this->mName.c_str(),
                            read_addr + read_offs, read_addr, i, ram_data,
                            this->mBus.port["req_valid"].U());
                        co_await this->clk->AStep(1);
                        read_offs = (read_offs + 8) % 64;
                    }
                    this->mBus.port["resp_valid"] = 0;
                }

                // 8 time write
                if (this->mBus.IsReqWriteBurst()) {
                    need_wait = false;
                    for (size_t i = 0; i < 8; i++) {
                        auto wdat_addr = this->mBus.port["req_bits_addr"];
                        auto wdat_data = this->mBus.port["req_bits_wdata"];
                        this->mMem.Write(wdat_addr.U() + (i * 8), 8, wdat_data);
                        this->mBus.RespWriteData(this->mBus.cmd_writeResp, 0);
                        this->mBus.port["resp_valid"] = 1; // send data
                        if (i == 7)
                            Assert(
                                this->mBus.IsReqWriteLast(),
                                "[%ld] %s.write burst need WriteLast(0x%x), ready=%lx, cmd=%lx",
                                this->clk->clk, this->mName.c_str(),
                                this->mBus.cmd_writeLast,
                                this->mBus.port["req_ready"].U(),
                                this->mBus.port["req_bits_cmd"].U());
                        DebugC(
                            this->debug,
                            "[%ld] %s.Write burst(%lx) at: %lx (%lx) [%d], data: 0x%lx, req valid=%lx, naddr: %lx ndata: %lx",
                            this->clk->clk, this->mName.c_str(),
                            this->mBus.port["req_bits_cmd"].U(),
                            wdat_addr.U() + (i * 8), wdat_addr.U(), (int)i,
                            wdat_data.U(), this->mBus.port["req_valid"].U(),
                            this->mBus.port["req_bits_addr"].U(),
                            wdat_data.U());
                        co_await this->clk->AStep(1);
                        while (!this->mBus.IsReqValid()) {
                            co_await this->clk->AStep(1);
                        }
                    }
                    this->mBus.port["resp_valid"] = 0;
                }

                if (this->mBus.IsReqWrite()) {
                    need_wait = false;
                    // MMIO write
                    auto w_addr = this->mBus.port["req_bits_addr"];
                    auto w_data = this->mBus.port["req_bits_wdata"];
                    this->mMem.Write(w_addr, 8, w_data);
                    this->mBus.RespWriteData(this->mBus.cmd_writeResp, 0);
                    this->mBus.port["resp_valid"] = 1;
                    DebugC(this->debug, "[%ld] %s.Write at: %lx, data: 0x%lx",
                           this->clk->clk, this->mName.c_str(), w_addr.U(),
                           w_data.U());
                    co_await this->clk->AStep(1);
                    this->mBus.port["resp_valid"] = 0;
                    // mmio read
                }
                if (this->mBus.IsReqRead()) {
                    need_wait   = false;
                    auto w_addr = this->mBus.port["req_bits_addr"];
                    auto w_data = this->mMem.Read(w_addr, 8);
                    // MMIO Read
                    this->mBus.RespReadData(this->mBus.cmd_readLast, w_data);
                    this->mBus.port["resp_valid"] = 1;
                    DebugC(this->debug, "[%ld] %s.Read at: %lx, data: 0x%lx",
                           this->clk->clk, this->mName.c_str(), w_addr.U(),
                           w_data);
                    co_await this->clk->AStep(1);
                    this->mBus.port["resp_valid"] = 0;
                }

                if (need_wait) co_await this->clk->AStep(1);
            }
        }
    };
#else
    class SimpleRamCor
    {
        xspcomm::XClock *clk;

    public:
        std::string mName;
        std::string mPrefix;
        SimpleBusWrapper mBus;
        SimpleMemory mMem;
        bool debug;
        SimpleRamCor(xspcomm::XClock &clock, std::string name, xspcomm::XPort &port,
                     std::string prefix = "", u_char default_value = 0,
                     bool debug = false) :
            mBus(port, prefix), mMem(name, 4, 1024, default_value), debug(debug)
        {
            Assert(false, "SimpleRamCor need c++20 support!");
        }
    };
#endif
} // namespace ovip

#endif
